Expression data and meta data is downloaded from GEO. Samples are quantile normalized and visualized with PCA and tSNE.

#load libraries
library(GEOquery)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Setting options('download.file.method.GEOquery'='auto')
Setting options('GEOquery.inmemory.gpl'=FALSE)
library(preprocessCore)
library(ggplot2)
library(Rtsne)
library(plotly)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(ggthemes)
#downlaod data from GEO
gse <- getGEO('GSE50588')[[1]]
Found 1 file(s)
GSE50588_series_matrix.txt.gz
trying URL 'https://ftp.ncbi.nlm.nih.gov/geo/series/GSE50nnn/GSE50588/matrix/GSE50588_series_matrix.txt.gz'
Content type 'application/x-gzip' length 14767110 bytes (14.1 MB)
==================================================
downloaded 14.1 MB

Parsed with column specification:
cols(
  .default = col_double(),
  ID_REF = col_character()
)
See spec(...) for full column specifications.
File stored at: 
/var/folders/vr/g6hydgcs71s5t3jc2xvcp_cm0000gn/T//RtmppNsuQ6/GPL10558.soft
3270 parsing failures.
  row        col           expected    actual         file
29537 Unigene_ID 1/0/T/F/TRUE/FALSE Hs.571610 literal data
29538 Unigene_ID 1/0/T/F/TRUE/FALSE Hs.545780 literal data
29539 Unigene_ID 1/0/T/F/TRUE/FALSE Hs.554603 literal data
29540 Unigene_ID 1/0/T/F/TRUE/FALSE Hs.437179 literal data
29541 Unigene_ID 1/0/T/F/TRUE/FALSE Hs.128234 literal data
..... .......... .................. ......... ............
See problems(...) for more details.
#parse expression matrix
exp = gse@assayData$exprs
#quantile normalize expression matrix
norm_exp = normalize.quantiles(exp)
colnames(norm_exp) = colnames(exp)
rownames(norm_exp) = rownames(exp)
#parse metadata dataframe
meta = gse@phenoData@data
#compute PCs
pca_norm = prcomp(norm_exp)$rotation[,1:3]
#add relevant metadata to pca
pca_norm = cbind(pca_norm,data.frame(target_gene = meta[match(rownames(pca_norm),rownames(meta)),c("target gene:ch1")], stringsAsFactors = F))
ggplot(as.data.frame(pca_norm),aes(x = PC2, y = PC3, color = target_gene)) + geom_point()

#compute tsne
tsne_norm = Rtsne(t(norm_exp),dim = 3,perplexity = 6)$Y
colnames(tsne_norm) = c("tSNE1","tSNE2","tSNE3")
rownames(tsne_norm) = colnames(norm_exp)
#add relevant metadata to tsne
tsne_norm = cbind(tsne_norm,data.frame(target_gene = meta[match(rownames(tsne_norm),rownames(meta)),c("target gene:ch1")], stringsAsFactors = F))
ggplot(as.data.frame(tsne_norm),aes(x = tSNE1, y = tSNE2, color = target_gene)) + geom_point()

plot_ly(tsne_norm, x = ~tSNE1, y = ~tSNE2, z = ~tSNE3, color = ~target_gene)
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

Z-score samples and visualize

znorm_exp = t(scale(t(norm_exp), center = T, scale = T))
#compute tsne
tsne_znorm = Rtsne(t(znorm_exp),dim = 3,perplexity = 3,theta = 0.001)$Y
colnames(tsne_znorm) = c("tSNE1","tSNE2","tSNE3")
rownames(tsne_znorm) = colnames(znorm_exp)
#add relevant metadata to tsne
tsne_znorm = cbind(tsne_znorm,data.frame(target_gene = meta[match(rownames(tsne_znorm),rownames(meta)),c("target gene:ch1")], stringsAsFactors = F))
ggplot(as.data.frame(tsne_znorm),aes(x = tSNE1, y = tSNE2, color = target_gene)) + geom_point() + theme_few() 

plot_ly(tsne_znorm, x = ~tSNE1, y = ~tSNE2, z = ~tSNE3, color = ~target_gene)
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

Generate Signatures

library(GeoDE)
Loading required package: Matrix
Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:plotly’:

    select
library(matrixStats)

Attaching package: ‘matrixStats’

The following objects are masked from ‘package:Biobase’:

    anyMissing, rowMedians
library(limma)
#map Illumina probes to gene ids
library(illuminaHumanv4.db)
Loading required package: AnnotationDbi
Loading required package: stats4
Loading required package: IRanges
Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following object is masked from ‘package:Matrix’:

    expand

The following object is masked from ‘package:plotly’:

    rename

The following object is masked from ‘package:base’:

    expand.grid


Attaching package: ‘IRanges’

The following object is masked from ‘package:plotly’:

    slice


Attaching package: ‘AnnotationDbi’

The following object is masked from ‘package:MASS’:

    select

The following object is masked from ‘package:plotly’:

    select

Loading required package: org.Hs.eg.db
gene_names = data.frame(Gene=unlist(mget(x = rownames(norm_exp),envir = illuminaHumanv4SYMBOL)))
#remove probes without mapped genes
norm_exp_df=as.data.frame(norm_exp)
norm_exp_df$gene_names = as.character(gene_names[,1])
norm_exp_df = norm_exp_df[!is.na(norm_exp_df$gene_names),]
rm(gene_names,norm_exp)
#where probes map to same genes, chose probes with greater variance
norm_exp_df = norm_exp_df[order(rowVars(as.matrix(norm_exp_df[,setdiff(colnames(norm_exp_df),"gene_names")])), decreasing = T),]
norm_exp_df = norm_exp_df[!duplicated(norm_exp_df$gene_names),]
# norm_exp_df = plyr::ddply(norm_exp_df,plyr::.(gene_names),function(exp_sub){
#   if(nrow(exp_sub)>1){
#     exp_sub_mat = exp_sub
#     exp_sub_mat$gene_names = NULL
#     exp_sub_mat = as.matrix(exp_sub_mat)
#     return(exp_sub[which.max(rowVars(exp_sub_mat)),])
#   }else{
#     return(exp_sub)
#   }
# })
rownames(norm_exp_df) = norm_exp_df$gene_names
norm_exp_df$gene_names = NULL
##write metadata and normalized expression to file
write.table(meta,"/Volumes/My Passport/ChromNet/Cusanovich/meta.tsv",row.names = T, col.names = T, sep = "\t", quote = T)
write.table(norm_exp_df, "/Volumes/My Passport/ChromNet/Cusanovich/norm_exp.tsv", row.names = T, col.names = T, sep = "\t", quote = F)
#pull out control expression
ctl_accessions = meta[meta$`target gene:ch1`=="NS",]$geo_accession
norm_ctl_exp = norm_exp_df[,colnames(norm_exp_df) %in% ctl_accessions]
ctl_class = rep(1,ncol(norm_ctl_exp))
#generate chardir signature for each TF knockdown
chardir_sigs = plyr::dlply(meta[meta$`target gene:ch1`!="NS",],
  plyr::.(`target gene:ch1`), function(tf){
    pert_accessions = tf$geo_accession
    norm_pert_exp = norm_exp_df[,colnames(norm_exp_df) %in% pert_accessions]
    pert_class = rep(2,ncol(norm_pert_exp))
    norm_instance_exp = cbind(norm_ctl_exp,norm_pert_exp)
    norm_instance_exp = cbind(data.frame(genenames = rownames(norm_instance_exp)),
      norm_instance_exp)
    instance_class = as.factor(c(ctl_class,pert_class))
    return(chdirAnalysis(norm_instance_exp,
      instance_class,
      list(1),
      CalculateSig = F)$results[[1]])
  
})

limma_sigs =  plyr::dlply(meta[meta$`target gene:ch1`!="NS",],
  plyr::.(`target gene:ch1`), function(tf){
    pert_accessions = tf$geo_accession
    norm_pert_exp = norm_exp_df[,colnames(norm_exp_df) %in% pert_accessions]
    pert_class = rep("pert",ncol(norm_pert_exp))
    norm_instance_exp = cbind(norm_ctl_exp,norm_pert_exp)
    design = data.frame(Control = c(rep(1,ncol(norm_ctl_exp)),rep(0,ncol(norm_pert_exp))), 
       Perturbation = c(rep(0, ncol(norm_ctl_exp)), rep(1, ncol(norm_pert_exp)))) 
     rownames(design) = colnames(norm_instance_exp) 
     fit = lmFit(norm_instance_exp, design=design) 
     cont.matrix = makeContrasts(PerturbationvsControl=Perturbation-Control, levels=design) 
     fit2 <- contrasts.fit(fit, cont.matrix) 
     fit = eBayes(fit2)
     return(topTable(fit, n = Inf))
})
limma_FC = plyr::ldply(limma_sigs,function(sig){
  df = data.frame(t(sig$logFC))
  colnames(df) = rownames(sig)
  return(df)
  
})
rownames(limma_FC) = limma_FC$`target gene:ch1`
limma_FC$`target gene:ch1` = NULL
limma_FC = as.data.frame(t(limma_FC))
limma_adj_pvals = plyr::ldply(limma_sigs,function(sig){
  df = data.frame(t(sig$P.Value))
  colnames(df) = rownames(sig)
  return(df)
})
rownames(limma_adj_pvals) = limma_adj_pvals$`target gene:ch1`
limma_adj_pvals$`target gene:ch1` = NULL
limma_adj_pvals = as.data.frame(t(limma_adj_pvals))
#write limma signature matrices to file
write.table(limma_FC,"/volumes/My Passport/ChromNet/Cusanovich/limma_sigs_FC.tsv", sep = "\t", quote = F, col.names = T, row.names = T)
write.table(limma_adj_pvals,"/volumes/My Passport/ChromNet/Cusanovich/limma_sigs_adj_pval.tsv", sep = "\t", quote = F, col.names = T, row.names = T)

Generate a signature matrix from the signature list

write.table(chardir_sigs_mat,"/Volumes/My Passport/Cusanovich/chardir_sigs_mat.tsv",sep = "\t", quote = F, row.names = T, col.names = T)
cannot open file '/Volumes/My Passport/Cusanovich/chardir_sigs_mat.tsv': No such file or directoryError in file(file, ifelse(append, "a", "w")) : 
  cannot open the connection
LS0tCnRpdGxlOiAiQ3VzYW5vdmljaCBldC4gYWwgU2lnbmF0dXJlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKRXhwcmVzc2lvbiBkYXRhIGFuZCBtZXRhIGRhdGEgaXMgZG93bmxvYWRlZCBmcm9tIEdFTy4gU2FtcGxlcyBhcmUgcXVhbnRpbGUgbm9ybWFsaXplZCBhbmQgdmlzdWFsaXplZCB3aXRoIFBDQSBhbmQgdFNORS4KCmBgYHtyfQojbG9hZCBsaWJyYXJpZXMKbGlicmFyeShHRU9xdWVyeSkKbGlicmFyeShwcmVwcm9jZXNzQ29yZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KFJ0c25lKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShnZ3RoZW1lcykKCiNkb3dubGFvZCBkYXRhIGZyb20gR0VPCmdzZSA8LSBnZXRHRU8oJ0dTRTUwNTg4JylbWzFdXQoKI3BhcnNlIGV4cHJlc3Npb24gbWF0cml4CmV4cCA9IGdzZUBhc3NheURhdGEkZXhwcnMKCiNxdWFudGlsZSBub3JtYWxpemUgZXhwcmVzc2lvbiBtYXRyaXgKbm9ybV9leHAgPSBub3JtYWxpemUucXVhbnRpbGVzKGV4cCkKY29sbmFtZXMobm9ybV9leHApID0gY29sbmFtZXMoZXhwKQpyb3duYW1lcyhub3JtX2V4cCkgPSByb3duYW1lcyhleHApCgojcGFyc2UgbWV0YWRhdGEgZGF0YWZyYW1lCm1ldGEgPSBnc2VAcGhlbm9EYXRhQGRhdGEKCiNjb21wdXRlIFBDcwpwY2Ffbm9ybSA9IHByY29tcChub3JtX2V4cCkkcm90YXRpb25bLDE6M10KCiNhZGQgcmVsZXZhbnQgbWV0YWRhdGEgdG8gcGNhCnBjYV9ub3JtID0gY2JpbmQocGNhX25vcm0sZGF0YS5mcmFtZSh0YXJnZXRfZ2VuZSA9IG1ldGFbbWF0Y2gocm93bmFtZXMocGNhX25vcm0pLHJvd25hbWVzKG1ldGEpKSxjKCJ0YXJnZXQgZ2VuZTpjaDEiKV0sIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKCgpnZ3Bsb3QoYXMuZGF0YS5mcmFtZShwY2Ffbm9ybSksYWVzKHggPSBQQzIsIHkgPSBQQzMsIGNvbG9yID0gdGFyZ2V0X2dlbmUpKSArIGdlb21fcG9pbnQoKQoKI2NvbXB1dGUgdHNuZQp0c25lX25vcm0gPSBSdHNuZSh0KG5vcm1fZXhwKSxkaW0gPSAzLHBlcnBsZXhpdHkgPSA2KSRZCmNvbG5hbWVzKHRzbmVfbm9ybSkgPSBjKCJ0U05FMSIsInRTTkUyIiwidFNORTMiKQpyb3duYW1lcyh0c25lX25vcm0pID0gY29sbmFtZXMobm9ybV9leHApCgojYWRkIHJlbGV2YW50IG1ldGFkYXRhIHRvIHRzbmUKdHNuZV9ub3JtID0gY2JpbmQodHNuZV9ub3JtLGRhdGEuZnJhbWUodGFyZ2V0X2dlbmUgPSBtZXRhW21hdGNoKHJvd25hbWVzKHRzbmVfbm9ybSkscm93bmFtZXMobWV0YSkpLGMoInRhcmdldCBnZW5lOmNoMSIpXSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpKQoKZ2dwbG90KGFzLmRhdGEuZnJhbWUodHNuZV9ub3JtKSxhZXMoeCA9IHRTTkUxLCB5ID0gdFNORTIsIGNvbG9yID0gdGFyZ2V0X2dlbmUpKSArIGdlb21fcG9pbnQoKQpwbG90X2x5KHRzbmVfbm9ybSwgeCA9IH50U05FMSwgeSA9IH50U05FMiwgeiA9IH50U05FMywgY29sb3IgPSB+dGFyZ2V0X2dlbmUpCmBgYAoKWi1zY29yZSBzYW1wbGVzIGFuZCB2aXN1YWxpemUgCmBgYHtyfQp6bm9ybV9leHAgPSB0KHNjYWxlKHQobm9ybV9leHApLCBjZW50ZXIgPSBULCBzY2FsZSA9IFQpKQoKI2NvbXB1dGUgdHNuZQp0c25lX3pub3JtID0gUnRzbmUodCh6bm9ybV9leHApLGRpbSA9IDMscGVycGxleGl0eSA9IDMsdGhldGEgPSAwLjAwMSkkWQpjb2xuYW1lcyh0c25lX3pub3JtKSA9IGMoInRTTkUxIiwidFNORTIiLCJ0U05FMyIpCnJvd25hbWVzKHRzbmVfem5vcm0pID0gY29sbmFtZXMoem5vcm1fZXhwKQoKI2FkZCByZWxldmFudCBtZXRhZGF0YSB0byB0c25lCnRzbmVfem5vcm0gPSBjYmluZCh0c25lX3pub3JtLGRhdGEuZnJhbWUodGFyZ2V0X2dlbmUgPSBtZXRhW21hdGNoKHJvd25hbWVzKHRzbmVfem5vcm0pLHJvd25hbWVzKG1ldGEpKSxjKCJ0YXJnZXQgZ2VuZTpjaDEiKV0sIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKHRzbmVfem5vcm0pLGFlcyh4ID0gdFNORTEsIHkgPSB0U05FMiwgY29sb3IgPSB0YXJnZXRfZ2VuZSkpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfZmV3KCkgCnBsb3RfbHkodHNuZV96bm9ybSwgeCA9IH50U05FMSwgeSA9IH50U05FMiwgeiA9IH50U05FMywgY29sb3IgPSB+dGFyZ2V0X2dlbmUpCgpgYGAKCkdlbmVyYXRlIFNpZ25hdHVyZXMKYGBge3J9CmxpYnJhcnkoR2VvREUpCmxpYnJhcnkobWF0cml4U3RhdHMpCmxpYnJhcnkobGltbWEpCiNtYXAgSWxsdW1pbmEgcHJvYmVzIHRvIGdlbmUgaWRzCmxpYnJhcnkoaWxsdW1pbmFIdW1hbnY0LmRiKQpnZW5lX25hbWVzID0gZGF0YS5mcmFtZShHZW5lPXVubGlzdChtZ2V0KHggPSByb3duYW1lcyhub3JtX2V4cCksZW52aXIgPSBpbGx1bWluYUh1bWFudjRTWU1CT0wpKSkKCiNyZW1vdmUgcHJvYmVzIHdpdGhvdXQgbWFwcGVkIGdlbmVzCm5vcm1fZXhwX2RmPWFzLmRhdGEuZnJhbWUobm9ybV9leHApCm5vcm1fZXhwX2RmJGdlbmVfbmFtZXMgPSBhcy5jaGFyYWN0ZXIoZ2VuZV9uYW1lc1ssMV0pCm5vcm1fZXhwX2RmID0gbm9ybV9leHBfZGZbIWlzLm5hKG5vcm1fZXhwX2RmJGdlbmVfbmFtZXMpLF0Kcm0oZ2VuZV9uYW1lcyxub3JtX2V4cCkKI3doZXJlIHByb2JlcyBtYXAgdG8gc2FtZSBnZW5lcywgY2hvc2UgcHJvYmVzIHdpdGggZ3JlYXRlciB2YXJpYW5jZQpub3JtX2V4cF9kZiA9IG5vcm1fZXhwX2RmW29yZGVyKHJvd1ZhcnMoYXMubWF0cml4KG5vcm1fZXhwX2RmWyxzZXRkaWZmKGNvbG5hbWVzKG5vcm1fZXhwX2RmKSwiZ2VuZV9uYW1lcyIpXSkpLCBkZWNyZWFzaW5nID0gVCksXQpub3JtX2V4cF9kZiA9IG5vcm1fZXhwX2RmWyFkdXBsaWNhdGVkKG5vcm1fZXhwX2RmJGdlbmVfbmFtZXMpLF0KCgojIG5vcm1fZXhwX2RmID0gcGx5cjo6ZGRwbHkobm9ybV9leHBfZGYscGx5cjo6LihnZW5lX25hbWVzKSxmdW5jdGlvbihleHBfc3ViKXsKIyAgIGlmKG5yb3coZXhwX3N1Yik+MSl7CiMgICAgIGV4cF9zdWJfbWF0ID0gZXhwX3N1YgojICAgICBleHBfc3ViX21hdCRnZW5lX25hbWVzID0gTlVMTAojICAgICBleHBfc3ViX21hdCA9IGFzLm1hdHJpeChleHBfc3ViX21hdCkKIyAgICAgcmV0dXJuKGV4cF9zdWJbd2hpY2gubWF4KHJvd1ZhcnMoZXhwX3N1Yl9tYXQpKSxdKQojICAgfWVsc2V7CiMgICAgIHJldHVybihleHBfc3ViKQojICAgfQojIH0pCgpyb3duYW1lcyhub3JtX2V4cF9kZikgPSBub3JtX2V4cF9kZiRnZW5lX25hbWVzCm5vcm1fZXhwX2RmJGdlbmVfbmFtZXMgPSBOVUxMCgojI3dyaXRlIG1ldGFkYXRhIGFuZCBub3JtYWxpemVkIGV4cHJlc3Npb24gdG8gZmlsZQp3cml0ZS50YWJsZShtZXRhLCIvVm9sdW1lcy9NeSBQYXNzcG9ydC9DaHJvbU5ldC9DdXNhbm92aWNoL21ldGEudHN2Iixyb3cubmFtZXMgPSBULCBjb2wubmFtZXMgPSBULCBzZXAgPSAiXHQiLCBxdW90ZSA9IFQpCndyaXRlLnRhYmxlKG5vcm1fZXhwX2RmLCAiL1ZvbHVtZXMvTXkgUGFzc3BvcnQvQ2hyb21OZXQvQ3VzYW5vdmljaC9ub3JtX2V4cC50c3YiLCByb3cubmFtZXMgPSBULCBjb2wubmFtZXMgPSBULCBzZXAgPSAiXHQiLCBxdW90ZSA9IEYpCgojcHVsbCBvdXQgY29udHJvbCBleHByZXNzaW9uCmN0bF9hY2Nlc3Npb25zID0gbWV0YVttZXRhJGB0YXJnZXQgZ2VuZTpjaDFgPT0iTlMiLF0kZ2VvX2FjY2Vzc2lvbgpub3JtX2N0bF9leHAgPSBub3JtX2V4cF9kZlssY29sbmFtZXMobm9ybV9leHBfZGYpICVpbiUgY3RsX2FjY2Vzc2lvbnNdCmN0bF9jbGFzcyA9IHJlcCgxLG5jb2wobm9ybV9jdGxfZXhwKSkKCiNnZW5lcmF0ZSBjaGFyZGlyIHNpZ25hdHVyZSBmb3IgZWFjaCBURiBrbm9ja2Rvd24KY2hhcmRpcl9zaWdzID0gcGx5cjo6ZGxwbHkobWV0YVttZXRhJGB0YXJnZXQgZ2VuZTpjaDFgIT0iTlMiLF0sCiAgcGx5cjo6LihgdGFyZ2V0IGdlbmU6Y2gxYCksIGZ1bmN0aW9uKHRmKXsKICAgIHBlcnRfYWNjZXNzaW9ucyA9IHRmJGdlb19hY2Nlc3Npb24KICAgIG5vcm1fcGVydF9leHAgPSBub3JtX2V4cF9kZlssY29sbmFtZXMobm9ybV9leHBfZGYpICVpbiUgcGVydF9hY2Nlc3Npb25zXQogICAgcGVydF9jbGFzcyA9IHJlcCgyLG5jb2wobm9ybV9wZXJ0X2V4cCkpCiAgICBub3JtX2luc3RhbmNlX2V4cCA9IGNiaW5kKG5vcm1fY3RsX2V4cCxub3JtX3BlcnRfZXhwKQogICAgbm9ybV9pbnN0YW5jZV9leHAgPSBjYmluZChkYXRhLmZyYW1lKGdlbmVuYW1lcyA9IHJvd25hbWVzKG5vcm1faW5zdGFuY2VfZXhwKSksCiAgICAgIG5vcm1faW5zdGFuY2VfZXhwKQogICAgaW5zdGFuY2VfY2xhc3MgPSBhcy5mYWN0b3IoYyhjdGxfY2xhc3MscGVydF9jbGFzcykpCiAgICByZXR1cm4oY2hkaXJBbmFseXNpcyhub3JtX2luc3RhbmNlX2V4cCwKICAgICAgaW5zdGFuY2VfY2xhc3MsCiAgICAgIGxpc3QoMSksCiAgICAgIENhbGN1bGF0ZVNpZyA9IEYpJHJlc3VsdHNbWzFdXSkKICAKfSkKCmxpbW1hX3NpZ3MgPSAgcGx5cjo6ZGxwbHkobWV0YVttZXRhJGB0YXJnZXQgZ2VuZTpjaDFgIT0iTlMiLF0sCiAgcGx5cjo6LihgdGFyZ2V0IGdlbmU6Y2gxYCksIGZ1bmN0aW9uKHRmKXsKICAgIHBlcnRfYWNjZXNzaW9ucyA9IHRmJGdlb19hY2Nlc3Npb24KICAgIG5vcm1fcGVydF9leHAgPSBub3JtX2V4cF9kZlssY29sbmFtZXMobm9ybV9leHBfZGYpICVpbiUgcGVydF9hY2Nlc3Npb25zXQogICAgcGVydF9jbGFzcyA9IHJlcCgicGVydCIsbmNvbChub3JtX3BlcnRfZXhwKSkKICAgIG5vcm1faW5zdGFuY2VfZXhwID0gY2JpbmQobm9ybV9jdGxfZXhwLG5vcm1fcGVydF9leHApCiAgICBkZXNpZ24gPSBkYXRhLmZyYW1lKENvbnRyb2wgPSBjKHJlcCgxLG5jb2wobm9ybV9jdGxfZXhwKSkscmVwKDAsbmNvbChub3JtX3BlcnRfZXhwKSkpLCAKICAgICAgIFBlcnR1cmJhdGlvbiA9IGMocmVwKDAsIG5jb2wobm9ybV9jdGxfZXhwKSksIHJlcCgxLCBuY29sKG5vcm1fcGVydF9leHApKSkpIAogICAgIHJvd25hbWVzKGRlc2lnbikgPSBjb2xuYW1lcyhub3JtX2luc3RhbmNlX2V4cCkgCiAgICAgZml0ID0gbG1GaXQobm9ybV9pbnN0YW5jZV9leHAsIGRlc2lnbj1kZXNpZ24pIAogICAgIGNvbnQubWF0cml4ID0gbWFrZUNvbnRyYXN0cyhQZXJ0dXJiYXRpb252c0NvbnRyb2w9UGVydHVyYmF0aW9uLUNvbnRyb2wsIGxldmVscz1kZXNpZ24pIAogICAgIGZpdDIgPC0gY29udHJhc3RzLmZpdChmaXQsIGNvbnQubWF0cml4KSAKICAgICBmaXQgPSBlQmF5ZXMoZml0MikKICAgICByZXR1cm4odG9wVGFibGUoZml0LCBuID0gSW5mKSkKfSkKCmxpbW1hX0ZDID0gcGx5cjo6bGRwbHkobGltbWFfc2lncyxmdW5jdGlvbihzaWcpewogIGRmID0gZGF0YS5mcmFtZSh0KHNpZyRsb2dGQykpCiAgY29sbmFtZXMoZGYpID0gcm93bmFtZXMoc2lnKQogIHJldHVybihkZikKICAKfSkKCnJvd25hbWVzKGxpbW1hX0ZDKSA9IGxpbW1hX0ZDJGB0YXJnZXQgZ2VuZTpjaDFgCmxpbW1hX0ZDJGB0YXJnZXQgZ2VuZTpjaDFgID0gTlVMTApsaW1tYV9GQyA9IGFzLmRhdGEuZnJhbWUodChsaW1tYV9GQykpCgpsaW1tYV9hZGpfcHZhbHMgPSBwbHlyOjpsZHBseShsaW1tYV9zaWdzLGZ1bmN0aW9uKHNpZyl7CiAgZGYgPSBkYXRhLmZyYW1lKHQoc2lnJFAuVmFsdWUpKQogIGNvbG5hbWVzKGRmKSA9IHJvd25hbWVzKHNpZykKICByZXR1cm4oZGYpCn0pCgpyb3duYW1lcyhsaW1tYV9hZGpfcHZhbHMpID0gbGltbWFfYWRqX3B2YWxzJGB0YXJnZXQgZ2VuZTpjaDFgCmxpbW1hX2Fkal9wdmFscyRgdGFyZ2V0IGdlbmU6Y2gxYCA9IE5VTEwKbGltbWFfYWRqX3B2YWxzID0gYXMuZGF0YS5mcmFtZSh0KGxpbW1hX2Fkal9wdmFscykpCgojd3JpdGUgbGltbWEgc2lnbmF0dXJlIG1hdHJpY2VzIHRvIGZpbGUKd3JpdGUudGFibGUobGltbWFfRkMsIi92b2x1bWVzL015IFBhc3Nwb3J0L0Nocm9tTmV0L0N1c2Fub3ZpY2gvbGltbWFfc2lnc19GQy50c3YiLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEYsIGNvbC5uYW1lcyA9IFQsIHJvdy5uYW1lcyA9IFQpCgp3cml0ZS50YWJsZShsaW1tYV9hZGpfcHZhbHMsIi92b2x1bWVzL015IFBhc3Nwb3J0L0Nocm9tTmV0L0N1c2Fub3ZpY2gvbGltbWFfc2lnc19hZGpfcHZhbC50c3YiLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEYsIGNvbC5uYW1lcyA9IFQsIHJvdy5uYW1lcyA9IFQpCgoKYGBgCgoKR2VuZXJhdGUgYSBzaWduYXR1cmUgbWF0cml4IGZyb20gdGhlIHNpZ25hdHVyZSBsaXN0CmBgYHtyfQoKb3JkZXJlZF9jaGFyZGlyX3NpZ3MgPSBsYXBwbHkoY2hhcmRpcl9zaWdzLGZ1bmN0aW9uKHNpZyl7CiAgcmV0dXJuKHNpZ1tvcmRlcihuYW1lcyhzaWcpKV0pCn0pCmNoYXJkaXJfc2lnc19tYXQgPSBkby5jYWxsKCJyYmluZCIsb3JkZXJlZF9jaGFyZGlyX3NpZ3MpCgp3cml0ZS50YWJsZShjaGFyZGlyX3NpZ3NfbWF0LCIvVm9sdW1lcy9NeSBQYXNzcG9ydC9DaHJvbU5ldC9DdXNhbm92aWNoL2NoYXJkaXJfc2lnc19tYXQudHN2IixzZXAgPSAiXHQiLCBxdW90ZSA9IEYsIHJvdy5uYW1lcyA9IFQsIGNvbC5uYW1lcyA9IFQpCgpgYGAKCg==